package com.salmon.security.app;

import com.salmon.security.core.properties.OAuth2ClientProperties;
import com.salmon.security.core.properties.SecurityProperties;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.builders.JdbcClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * 基于OAuth2的授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class SalmonAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;

    private final TokenStore tokenStore;

    private final UserDetailsService userDetailsService;

    private final SecurityProperties securityProperties;

    private final DataSource dataSource;

    private final PasswordEncoder passwordEncoder;

    @Autowired(required = false)
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired(required = false)
    private TokenEnhancer jwtTokenEnhancer;

    public SalmonAuthorizationServerConfig(AuthenticationConfiguration authenticationConfiguration,
                                           ObjectProvider<TokenStore> tokenStore,
                                           UserDetailsService userDetailsService,
                                           SecurityProperties securityProperties,
                                           PasswordEncoder passwordEncoder,
                                           DataSource dataSource
    ) throws Exception {
        this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
        this.tokenStore = tokenStore.getIfAvailable();
        this.userDetailsService = userDetailsService;
        this.securityProperties = securityProperties;
        this.passwordEncoder = passwordEncoder;
        this.dataSource = dataSource;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(this.authenticationManager).userDetailsService(this.userDetailsService);
        if (this.tokenStore != null) {
            endpoints.tokenStore(this.tokenStore);
        }
        if(jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancers = new ArrayList<>();
            enhancers.add(jwtTokenEnhancer);
            enhancers.add(jwtAccessTokenConverter);
            tokenEnhancerChain.setTokenEnhancers(enhancers);
            endpoints.tokenEnhancer(tokenEnhancerChain)
                    .accessTokenConverter(jwtAccessTokenConverter);
        }
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        if(this.securityProperties.getOauth2().isClientDetailsInMemory()) {
            InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
            OAuth2ClientProperties[] auth2ClientProperties = securityProperties.getOauth2().getClients();
            if (ArrayUtils.isNotEmpty(auth2ClientProperties)) {
                for (OAuth2ClientProperties clientProperty : auth2ClientProperties) {
                    builder.withClient(clientProperty.getClientId())
                            .secret(securityProperties.getOauth2().isEnableClientSecretEncode() ? passwordEncoder.encode(clientProperty.getClientSecret()) : clientProperty.getClientSecret())
                            .accessTokenValiditySeconds(clientProperty.getAccessTokenValiditySeconds())
                            .refreshTokenValiditySeconds(2592000)
                            .authorizedGrantTypes("authorization_code",
                                    "password", "client_credentials", "implicit", "refresh_token")
                            .redirectUris("http://www.xxxx.cn")
                            .autoApprove(true)
                            .resourceIds(clientProperty.getClientId())
                            .authorities("ROLE_USER")
                            .additionalInformation(clientProperty.getClientId() + ":" + clientProperty.getClientSecret())
                            .scopes("all", "read", "write");
                }
            }
        } else {// 基于JDBC的方式存储客户端信息
            JdbcClientDetailsServiceBuilder builder = clients.jdbc(this.dataSource);
            //是否开启ClientSecret进行加密存储
            if(this.securityProperties.getOauth2().isEnableClientSecretEncode()) {
                builder.passwordEncoder(this.passwordEncoder);
            }
             //读取配置文件中配置的ClientDetail信息，当启动程序时，自动将数据插入到数据库中
            /*OAuth2ClientProperties[] auth2ClientProperties = securityProperties.getOauth2().getClients();
            if (ArrayUtils.isNotEmpty(auth2ClientProperties)) {
                for (OAuth2ClientProperties clientProperty : auth2ClientProperties) {
                    builder.withClient(clientProperty.getClientId())
                            .secret(clientProperty.getClientSecret())
                            .accessTokenValiditySeconds(clientProperty.getAccessTokenValiditySeconds())
                            .refreshTokenValiditySeconds(2592000)
                            .authorizedGrantTypes("authorization_code",
                                    "password", "refresh_token")
                             .redirectUris("http://www.xxxx.cn")
                            .autoApprove(true)
                            .resourceIds(clientProperty.getClientId())
                            .authorities("ROLE_USER")
                            .additionalInformation(clientProperty.getClientId() + ":" + clientProperty.getClientSecret())
                            .scopes("all", "read", "write");
                }
            }*/
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 设置clientSecret密码的加密方式，如果不设置此项，默认采用的是容器中注入的PasswordEncoder
        if(!securityProperties.getOauth2().isEnableClientSecretEncode()) { // 不采用加密方式存储
            security.passwordEncoder(NoOpPasswordEncoder.getInstance());
        }
        security.tokenKeyAccess("permitAll()") //允许所有资源服务器访问公钥端点（/oauth/token_key）
                .checkTokenAccess("isAuthenticated()") //允许验证用户访问令牌解析端点（/oauth/check_token）
                .allowFormAuthenticationForClients();// 允许客户端发送表单来进行权限认证来获取令牌
    }
}
